package com.hna.eking.AirlineServer.security;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.codehaus.groovy.util.Finalizable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hna.eking.AirlineServer.Utils.MD5Util;
import com.hna.eking.AirlineServer.Utils.ResponseSupport;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Configuration
@EnableWebSecurity
 //可以指定多个HttpSecurity实例，用@Order指定优先级
public class WebSecurityConfigToken extends WebSecurityConfigurerAdapter {
	
	Logger logger = LoggerFactory.getLogger(WebSecurityConfigToken.class);
	
	@Autowired
	CustomUserService customUserService;
	
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	
    	//使用and()方法相当于XML标签的关闭
        http.cors().and().authorizeRequests()
        	.antMatchers("/index", "/login/*", "/needlogin", "/hello", "/favicon.ico", "/file/**",
        			"/logout").permitAll()
        	.antMatchers("/airports", "/maps", "/api/*","/airport/*", "/mapinfo/*", "/codeinfo/*", "/logout").hasAnyRole("USER","ADMIN")
        	.anyRequest().authenticated()  
        	.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler("/accessDenied")) //登录后权限不够的处理方法
        	.and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint("/needlogin"))
        	.and()
        	.addFilterAt(deviceUserProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
            .addFilterAt(JWTtokenAuthenticationFilter(), BasicAuthenticationFilter.class)         
        	.csrf().disable()
            ;
//        http.addFilter(deviceUserProcessingFilter());
        	
    }

    @Override
    protected AuthenticationManager authenticationManager() {
      ProviderManager authenticationManager = new ProviderManager(Arrays.asList(deviceUserAuthenticationProvider()));
      //不擦除认证密码，擦除会导致TokenBasedRememberMeServices因为找不到Credentials再调用UserDetailsService而抛出UsernameNotFoundException
      authenticationManager.setEraseCredentialsAfterAuthentication(false);
      return authenticationManager;
    }
    
    @Bean
    DeviceUserAuthenticationProvider deviceUserAuthenticationProvider(){
    	DeviceUserAuthenticationProvider deviceUserAuthenticationProvider = new DeviceUserAuthenticationProvider();
    	deviceUserAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    	deviceUserAuthenticationProvider.setCustomUserService(customUserService);
      return deviceUserAuthenticationProvider;
    }
    
	@Bean
	@Order(1)
	DeviceUserProcessingFilter deviceUserProcessingFilter(){
		DeviceUserProcessingFilter deviceUserProcessingFilter = new DeviceUserProcessingFilter();
		deviceUserProcessingFilter.setAuthenticationManager(authenticationManager());
		deviceUserProcessingFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
		deviceUserProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
		return deviceUserProcessingFilter;
	}
	
	@Bean
	JWTAuthenticationFilter JWTtokenAuthenticationFilter(){
		JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
		filter.setAuthenticationFailureHandler(tokenFailureHandler());
		return filter;
	}
    
    @Bean
    AuthenticationSuccessHandler authenticationSuccessHandler() {
    	return new AuthenticationSuccessHandler(){
			@Override
			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
					Authentication authResult) throws IOException, ServletException {
				
				DeviceUser deviceUser  = (DeviceUser) authResult.getPrincipal();
				List<String> authorities = new ArrayList<String>();
				for (GrantedAuthority auth : deviceUser.getAuthorities()) {
					authorities.add(auth.getAuthority());
				}
				Map<String, Object> claims = new HashMap<String, Object> ();
				claims.put("id", deviceUser.getId());
				claims.put("username", deviceUser.getUsername());
				claims.put("uuid", deviceUser.getUuid());
				claims.put("authorities", authorities);
		        String token = Jwts.builder().setClaims(claims)  
		                .setSubject(deviceUser.getUsername())
		                .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000))  
		                .signWith(SignatureAlgorithm.HS512, "MyJwtSecret")  
		                .compact();  
		        response.addHeader("Authorization", "Bearer " + token);  
		        
				response.setCharacterEncoding("UTF-8");  
			    response.setContentType("application/json; charset=utf-8"); 
			    PrintWriter out = null;  
			    ObjectMapper mapper = new ObjectMapper();
			    ResponseSupport<DeviceUser> responseSupport = new ResponseSupport<DeviceUser>();
			    responseSupport.setCode(200);
			    responseSupport.setMessage("登录成功");
			    responseSupport.setContent(deviceUser);
			    
			    try {  
			        out = response.getWriter();
			        out.append(mapper.writeValueAsString(responseSupport));
			    } catch (IOException e) {  
			        e.printStackTrace();  
			    } finally {  
			        if (out != null) {  
			            out.close();  
			        }  
			    }  
			}
    	};
    }

    @Bean
    AuthenticationFailureHandler authenticationFailureHandler() {
    	SimpleUrlAuthenticationFailureHandler failureHandler = 
    			new SimpleUrlAuthenticationFailureHandler("/login/failure");
    	failureHandler.setUseForward(true);
    	return failureHandler;
    }
    
    @Bean
    AuthenticationFailureHandler tokenFailureHandler() {
    	SimpleUrlAuthenticationFailureHandler failureHandler = 
    			new SimpleUrlAuthenticationFailureHandler("/login/illegalToken");
    	failureHandler.setUseForward(true);
    	return failureHandler;
    }
    
    @Bean
    AccessDeniedHandler accessDeniedHandler(final String url) {
    	return new AccessDeniedHandler() {	
    		@Override
			public void handle(HttpServletRequest request, HttpServletResponse response,
    				AccessDeniedException accessDeniedException) throws IOException,
    				ServletException {
    			RequestDispatcher dispatcher = request.getRequestDispatcher(url);
    			dispatcher.forward(request, response);
    		}
    	};
    }
    
    @Bean
    AuthenticationEntryPoint authenticationEntryPoint(String url) {
		Assert.isTrue(
				StringUtils.hasText(url)
						&& UrlUtils.isValidRedirectUrl(url),
				"loginFormUrl must be specified and must be a valid redirect URL");
    	return new CustomAuthenticationEntryPoint(url);
    }
    
    
    @Bean
    BCryptPasswordEncoder passwordEncoder() {
    	return new BCryptPasswordEncoder(){
            @Override
            public String encode(CharSequence rawPassword) {
                return MD5Util.encode((String)rawPassword);
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(MD5Util.encode((String)rawPassword));
            };
    	};
    }
    
}